package net.momodalo.app.vimtouch;

import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.os.Handler;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;

import java.lang.reflect.Field;

import jackpal.androidterm.emulatorview.ColorScheme;
import jackpal.androidterm.emulatorview.EmulatorView;
import jackpal.androidterm.emulatorview.TermSession;

public class TermView extends EmulatorView implements
        GestureDetector.OnGestureListener {
    private TermSession mSession;
    private int mTopRow = 0; //we don't use termnial scroll
    private GestureDetector mGestureDetector;
    private ScaleDetectorCompat mScaleDetector;
    private boolean mSingleTapESC;
    private boolean mTouchGesture;
    VimSettings mSettings;
    private boolean mInserted = false;
    private Runnable mCheckRunnable;
    private Handler mCheckHandler;
    private int mCheckCount = 0;
    private VimInputConnection mInputConnection = null;
    private boolean mIMEComposing = false;
    private boolean mZoomBottom = true;
    private int mFontSize = 0;

    private static final int FLING_REFRESH_PERIOD = 50;
    private static final int SCREEN_CHECK_PERIOD = 1000;
    private static final int CURSOR_BLINK_PERIOD = 1000;
    private static final int VISUAL_MODE_PERIOD = 1000;

    public interface OnZoomListener {
        void onZoom(boolean on);
    }

    private OnZoomListener mZoomListener = null;
    public void setOnZoomListener(OnZoomListener listen) {
        mZoomListener = listen;
    }


    public boolean checkInsertMode() {
        boolean b = Exec.isInsertMode();
        if (b != mInserted){
            Context context = getContext();
            InputMethodManager imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.restartInput(TermView.this);
            mInserted = b;
            return true;
        }
        return false;
    }

    public TermView(Context context, TermSession session, DisplayMetrics metrics) {
        super(context, session, metrics);
        mSession = session;
        mGestureDetector = new GestureDetector(context, this);
        mScaleDetector = null;
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { // 2.2+
            mScaleDetector = new ScaleDetectorCompat(this);
        }
        mCheckRunnable = new Runnable() {
            public void run() {
                if(!checkInsertMode() && mCheckCount < 10)
                    mCheckHandler.postDelayed(mCheckRunnable, 100);
                mCheckCount++;
            }
        };
        mCheckHandler = new Handler();
    }

    public void updatePrefs(VimSettings settings, ColorScheme scheme) {
        if (scheme == null) {
            scheme = new ColorScheme(settings.getColorScheme());
        }

        if(mFontSize != settings.getFontSize()){
            setTextSize(settings.getFontSize());
            mFontSize = settings.getFontSize();
        }
        setCursorStyle(settings.getCursorStyle(), settings.getCursorBlink());
        setUseCookedIME(settings.useCookedIME());
        mIMEComposing = settings.useCookedIME();
        setColorScheme(scheme);
        setBackKeyCharacter(settings.getBackKeyCharacter());
        setControlKeyCode(settings.getControlKeyCode());
        setFnKeyCode(settings.getFnKeyCode());
        mZoomBottom = settings.getZoomBottom();
        mSingleTapESC = settings.getSingleTapESC();
        mTouchGesture = settings.getTouchGesture();

        mSettings = settings;
    }

    public void updatePrefs(VimSettings settings) {
        updatePrefs(settings, null);
    }

    // Begin GestureDetector.OnGestureListener methods
    /**
     * Our message handler class. Implements a periodic callback.
     */
    private final Handler mHandler = new Handler();

    public boolean onScroll(MotionEvent e1, MotionEvent e2,
            float distanceX, float distanceY) {
        /*
        distanceY += mScrollRemainder;
        int deltaRows = (int) (distanceY / getCharacterHeight());
        mScrollRemainder = distanceY - deltaRows * getCharacterHeight();
        mTopRow =
            Math.min(0, Math.max(-(mTranscriptScreen
                    .getActiveTranscriptRows()), mTopRow + deltaRows));
        */
        invalidate();
        return true;
    }

    float mScaleSpan = -1.0f;

    public boolean onSingleTapUp(MotionEvent ev) {
        Exec.mouseDown( mDownY, mDownX);
        Exec.mouseUp( mDownY, mDownX);
        if(mSingleTapESC)mSession.write(27);
        return true;
    }

    private float mVelocity = 0;

    private Runnable mFlingRun = new Runnable() {
        public void run() {
            Exec.scrollBy((int)mVelocity);
            if(mVelocity > 0){
                mVelocity -= mVelocity>2?2:mVelocity;
            }else{
                mVelocity -= mVelocity<-2?-2:mVelocity;
            }
            if(mVelocity == 0){
                if(mInputConnection!=null)mInputConnection.notifyTextChange();
                return;
            }
            mHandler.postDelayed(this, FLING_REFRESH_PERIOD);
        }
    };

    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
        mVelocity = -velocityY/(10*getCharacterHeight());
        mHandler.postDelayed(mFlingRun, FLING_REFRESH_PERIOD);
        return true;
    }

    int mDownX;
    int mDownY;

    public boolean onDown(MotionEvent ev) {
        float y = ev.getY();
        float x = ev.getX();
        int fingers = ev.getPointerCount();
        mLastY = y;
        mHandler.removeCallbacks(mFlingRun);

        return true;
    }

    // End GestureDetector.OnGestureListener methods
    float mLastY = -1;
    float mLastX = -1;
    int mVisualMode = 0;
    
    private void toggleVisualMode() {
        if(!Exec.isInsertMode()){
            switch(mVisualMode){
                case 0:
                    mVisualMode = 1;
                    mSession.write("v");
                    break;
                case 1:
                    mVisualMode = 2;
                    mSession.write((char) ('v' - 'a' + '\001'));
                    break;
                case 2:
                    mVisualMode = 1;
                    mSession.write("v");
                    break;
                default:
                    mVisualMode = 1;
                    mSession.write("v");
                    break;
            }
        }
    }

    private Runnable mVisualRun = new Runnable() {
        public void run() {
            toggleVisualMode();
        }
    };

    boolean mZoom = false;
    float mZoomX = 0.0f;
    float mZoomY = 0.0f;
    public void setZoom(boolean z){
        mZoom = z;
        if(mZoomListener != null) mZoomListener.onZoom(z);
    }

    public boolean getZoom(){
        return mZoom;
    }

    public void onLongPress(MotionEvent ev) {
        float y = ev.getY();
        float x = ev.getX();
        setZoom(true);
        mZoomX = x;
        mZoomY = y;

        mLastY = -1;
        Exec.mouseDown( (int)(y/getCharacterHeight()), (int)(x/getCharacterWidth()));
        Exec.mouseUp( (int)(y/getCharacterHeight()), (int)(x/getCharacterWidth()));
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        checkInsertMode();

        mHandler.removeCallbacks(mVisualRun);

        if(getSelectingText())
            return super.onTouchEvent(ev);
        float y = ev.getY();
        float x = ev.getX();
        int action = ev.getAction();
        int fingers = ev.getPointerCount();

        if(action == MotionEvent.ACTION_DOWN && fingers == 1){
            mLastY = y;
            mLastX = x;
            mDownX = (int)(x/getCharacterWidth());
            mDownY = (int)(y/getCharacterHeight());

        }else if (action == MotionEvent.ACTION_MOVE && fingers == 1 && mScaleSpan < 0.0){
            mZoomX = x;
            mZoomY = y;

            if(mLastX != -1 && Math.abs(x-mLastX) > getCharacterWidth() * 5 && !getZoom()){
                setZoom(true);
                mLastY = -1;
                int cursorX = (int)(x/getCharacterWidth());
                int cursorY = (int)(y/getCharacterHeight());
                Exec.setCursorPos(cursorY, cursorX);
            } else if(mLastY != -1 && ( mLastX == -1 || (Math.abs(y-mLastY) > getCharacterHeight() * 2)) && !getZoom()){
                int scrolls = (int)((mLastY - y)/getCharacterHeight());
                if(mTouchGesture){
                    if(mLastX != -1){
                        int cursorX = (int)(x/getCharacterWidth());
                        int cursorY = (int)(y/getCharacterHeight());
                        Exec.setCursorPos(cursorY, cursorX);
                    }
                    //Exec.mouseDown( mDownY, mDownX);
                    if(scrolls != 0)Exec.scrollBy(scrolls);
                    if(mInputConnection!=null)mInputConnection.notifyTextChange();
                }
                if(scrolls != 0)mLastY = y;
                mLastX = -1;
            }else if (getZoom()){
                long time = ev.getEventTime();
                int state = Exec.getState();

                if(mVisualMode > 0){
                    Exec.mouseDrag( (int)(y/getCharacterHeight()), (int)(x/getCharacterWidth()));
                    Exec.mouseDown( (int)(y/getCharacterHeight()), (int)(x/getCharacterWidth()));
                }else{
                    int cursorX = (int)(x/getCharacterWidth());
                    int cursorY = (int)(y/getCharacterHeight());
                    Exec.setCursorPos(cursorY, cursorX);
                }
                Exec.updateScreen();
                mHandler.postDelayed(mVisualRun, VISUAL_MODE_PERIOD);
            }
        }else if(action == MotionEvent.ACTION_UP){
            if(getZoom())
                Exec.mouseUp( (int)(y/getCharacterHeight()), (int)(x/getCharacterWidth()));
            mLastY = -1;
            mLastX = -1;
            setZoom(false);
            invalidate();
            mVisualMode = 0;
            if(mInputConnection!=null)mInputConnection.notifyTextChange();
        }
        if (mTouchGesture && mScaleSpan < 0.0) 
            mGestureDetector.onTouchEvent(ev);
        if (null != mScaleDetector) { //
            return mScaleDetector.onTouchEvent(ev);
        }
        return true;
    }

    public boolean dispatchKeyEvent(KeyEvent event){
        boolean b = super.dispatchKeyEvent(event);
        lateCheckInserted();
        return b;
    }

    public void lateCheckInserted(){
        //FIXME check the vim State change lately
        mCheckHandler.removeCallbacks(mCheckRunnable);
        if(!checkInsertMode()){
            mCheckCount = 0;
            mCheckHandler.postDelayed(mCheckRunnable, 100);
        }
    }

    public InputConnection onCreateInputConnection (EditorInfo outAttrs) {
        if(!Exec.isInsertMode() || !mIMEComposing){
            mInputConnection = null;
            return super.onCreateInputConnection(outAttrs);
        }
        outAttrs.actionLabel = null;
        outAttrs.inputType = InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
        outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
        mInputConnection = new VimInputConnection(this);
        return mInputConnection;
    }

    public boolean toggleIMEComposing(){
        mIMEComposing = mIMEComposing?false:true;
        Context context = getContext();
        InputMethodManager imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.restartInput(TermView.this);
        return mIMEComposing;
    }


    /*
    public boolean onCheckIsTextEditor () {
        return true;
    }*/

    private float mScaleX = 1.0f;
    private float mScaleY = 1.0f;
    private float mScalePX;
    private float mScalePY;

    public void setScale(float sx, float sy, float px, float py){
        mScaleX = sx;
        mScaleY = sy;
        mScalePX = px;
        mScalePY = py;
    }
 
    protected void onDraw(Canvas canvas) {

        if(mScaleX != 1.0 || mScaleY != 1.0){
            canvas.scale(mScaleX,mScaleY,mScalePX,mScalePY);
        }

        super.onDraw(canvas);
        if(mZoom){
            float tx = 2 * getCharacterWidth() * ( 1 - 2 * mZoomX / getVisibleWidth());
            float h = getCharacterHeight()*2;
            canvas.scale(3.0f,3.0f,mZoomX, mZoomY);
            if(mZoomY > getVisibleHeight()/2){
                canvas.translate( tx, (-mZoomY)/3 + h);
            }else{
                canvas.translate( tx, (getVisibleHeight()-mZoomY)/3 - h);
            }
            canvas.clipRect(mZoomX - mZoomX/3-5, mZoomY-h, mZoomX+(getVisibleWidth()-mZoomX)/3+5,mZoomY+h);
            super.onDraw(canvas);
        }
        if(mZoomBottom && Exec.isCmdLine()){
            float cx = getVisibleWidth() - (Exec.getCursorCol()+2) * getCharacterWidth() * 3.0f;
            canvas.scale(3.0f,3.0f, cx < 0.0f? cx : 0.0f, getVisibleHeight() - getCharacterHeight());
            canvas.translate( cx < 0.0f? cx : 0.0f, (- getVisibleHeight() + getCharacterHeight() * 2)/3);
            super.onDraw(canvas);
        }
    }

    public float getCharacterWidth(){
        try {
            Field privateField = 
               EmulatorView.class.getDeclaredField("mCharacterWidth");
            privateField.setAccessible(true);

            return privateField.getFloat(this);
        }catch(Exception e){
            return 0.0f;
        }
    }

    public float getCharacterHeight(){
        try{
            Field privateField = 
               EmulatorView.class.getDeclaredField("mCharacterHeight");
            privateField.setAccessible(true);

            return (float) privateField.getInt(this);
        }catch(Exception e){
            return 0.0f;
        }
    }
}
